using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Threading;
using log4net;
using SQLRunnerLib.Project;
using SQLRunnerLib.Services;
using SQLRunnerLib.TransMgr;
using SQLRunnerLib.Util;
using SQLServices.Log;
using SQLServices.Runners;
using SQLServices.Runners.Events;
using System.Text.RegularExpressions;
using SQLServices.TransMgr;

namespace SQLRunnerLib.Runners
{
	/// <summary>
	/// The main role of the Runner is to execute the scripts against the
	/// SQL Server database. The constructor requires a list of ScriptFolders.
	/// Call the RunScripts method to execute the scripts.
	/// The class can inform of the progress back to the caller using the four
	/// different types of events.
	/// </summary>
	public sealed class Runner : IRunner
	{

		#region Private Instances

		private readonly SQLProject _proj;
		private bool _isAsyncExecution;
		private bool _cancelExecution;
        private ITransManager _transMgr;
	    private ConnectionMgrType _transMgrType = ConnectionMgrType.CommitTransactionConnMgr;
        private readonly Dictionary<string, string> _placeholders;

	    private readonly ILog _logger = LogManager.GetLogger(typeof (Runner));
	    #endregion
		#region Events

		public event ProgressMsgCreatedEventHandler ProgressMsgCreated;
		public event ScriptExecErrorEventHandler ScriptExecError;
		public event ScriptExecutedEventHandler ScriptExecuted;
		public event ExecutionFinishedEventHandler ExecutionFinished;

		#endregion
		#region Constructors

		public Runner(SQLProject project)
		{
			_proj = project;

            try
            {
                PlaceHolderConfig phConfig = ConfigurationManager.GetSection("PlaceHolderConfig") as PlaceHolderConfig;
                if(phConfig != null)
                    _placeholders = phConfig.PlaceHoldersDictionary;
            }
            catch (ConfigurationErrorsException)
            {
                LogMessage("Failed to load Placeholder Configuration", MsgLevel.Error);
            }
            
			
		}

	    public ConnectionMgrType ConnectionMgrType
	    {
	        get { return _transMgrType; }
	        set
	        {
	            _transMgrType = value;
                ServiceContainer.Logger.LogMessage(
                    string.Format("Runner - Connection manager changed to {0}",
                    Enum.GetName(typeof(ConnectionMgrType), _transMgrType)),
                    MsgLevel.Info);
	        }
	    }

	    public bool IsCancelled
	    {
	        get { return _cancelExecution; }
	    }

	    #endregion
		#region Public Methods

        public void CancelExecution(string msg)
        {
            _cancelExecution = true;
            LogMessage(msg, MsgLevel.Info);
        }

		public void CancelExecution() {
            CancelExecution("Runner - Cancel execution message was received");
		}

	    public void RunScriptsAsync()
		{
			if ( _proj == null ) return;
			_isAsyncExecution = true;
			if ( ProgressMsgCreated != null )
			{
				ProgressMsgCreated(this, 
				new ProgressMsgCreatedEventArgs(
					"Connecting to '{0}' database in the '{1}' SQL server", 
					ServiceContainer.SQLConnection.GetDatabase().DatabaseName,
					ServiceContainer.SQLConnection.ServerName));
			}
			LogMessage("Runner - thread has started");
			RunScripts();
			return;
		}

		public void RunScripts() {
			if ( _proj == null ) return;
            // clone the connection so this thread owns the SQLServer object rather 
            // than the UI
            _transMgr = FactoryTransMgr.GetInstance(ConnectionMgrType, this);
			try
			{
				foreach (Folder folder in _proj.Folders) {
					LogMessage(String.Format("Runner - processing folder {0}", folder.Path));
					ExecFolder(folder, ServiceContainer.CreatePathItem(folder.Path, null), _proj);
				}
			}
			catch
			{
				if ( ProgressMsgCreated != null )
				{
					string msg = "Runner - Script processor was interrupted";
					ProgressMsgCreated(this, new ProgressMsgCreatedEventArgs(msg));
					LogMessage(msg, MsgLevel.Info);
				}
			}
			finally
			{
				if ( ExecutionFinished != null ) 
				{
					ExecutionFinished(this, new ExecutionFinishedEventArgs(_cancelExecution));
				}
				if ( _isAsyncExecution )
				{
					LogMessage("Runner - thread has finished");
				}
			}
			return;
		}

		#endregion
		#region Private Methods

        private static void  LogMessage(string msg)
        {
            ServiceContainer.Logger.LogMessage(msg);
        }
        private static void LogMessage(string msg, MsgLevel level)
        {
            ServiceContainer.Logger.LogMessage(msg, level);
        }

		private void ExecFolder(Folder folder, IPathItem pathItem, SQLProject project)
		{
			ArrayList files = SQLProject.FilesInRootFolder(folder, project);

			ServiceContainer.EventLogger.DefaultPath = pathItem;
			foreach (FileInfo file in files)
			{
				LogMessage(String.Format("Runner - calling ExecuteSQL method for script {0}", file.Name));
				ExecuteSQL(file);
				if ( _isAsyncExecution ) Thread.Sleep(10);
				if ( IsCancelled ) return;
			}
			// check if we need to run the subfolders
			if (!folder.IncludeAllSubFolders) return;
			foreach (Folder subfolder in folder.Folders)
			{
				DirectoryInfo dir = new DirectoryInfo(subfolder.Path);
				if ( !dir.Exists ) continue;
				ExecFolder(subfolder, ServiceContainer.CreatePathItem(dir.Name, pathItem), project);
				if ( IsCancelled ) return;
			}
		}

		private void ExecuteSQL(FileInfo aFile)
		{
			if ( IsCancelled ) return;
            try
            {
                OnProgressMsgCreated("Executing {0} script", aFile.Name);
                using (StreamReader aStream = new StreamReader(aFile.OpenRead()))
                {
                    try
                    {
                        string strSQL = aStream.ReadToEnd();
                        aStream.Close();

                        strSQL = ReplacePlaceholders(strSQL, aFile.Name);
                        _logger.Debug(strSQL);

                        _transMgr.ExecuteSQL(strSQL);
                        OnScriptExecuted(aFile.Name);
                    }
                    catch (Exception e)
                    {
                        OnProgressMsgCreated("Error when executing the {0} script file", aFile.Name);
                        OnScriptError(aFile.Name, e.Message);
                    }
                }
            }
            catch (Exception e)
            {
                OnProgressMsgCreated("Error when handling the {0} script file", aFile.Name);
                OnScriptError(aFile.Name, e.Message);
            }
		}

        private string ReplacePlaceholders(string strSQL, string strFilename)
        {
            if (_placeholders != null)
            {
                try
                {
                    foreach (KeyValuePair<string, string> kvp in _placeholders)
                        strSQL = Regex.Replace(strSQL, kvp.Key, kvp.Value, RegexOptions.IgnoreCase);
                }
                catch (Exception ex)
                {
                    OnProgressMsgCreated("Error replacing placeholders in script {0}", strFilename);
                    OnScriptError(strFilename, ex.Message);
                }
            }
            return strSQL;
        }

		private void OnProgressMsgCreated( string msg, params object[] args)
		{
			if ( ProgressMsgCreated != null )
			{
				ProgressMsgCreated(this, new ProgressMsgCreatedEventArgs(msg, args));
			}
		}

		private void OnScriptExecuted(string fileName)
		{
			if ( ScriptExecuted != null)
			{
				ScriptExecuted(this, new ScriptExecutedEventArgs(fileName));
			}
		}

		private void OnScriptError(string fileName, string errorMsg)
		{
			if ( ScriptExecError != null)
			{
				ScriptExecError(this, new ScriptExecutionErrorEventArgs(fileName, errorMsg));
			}
		}

		#endregion

	}
}